﻿using UnityEditor;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UIElements;
using winS.UnityEditor.UIElement;
using Column = winS.UnityEditor.UIElement.Column;
using Label = winS.UnityEditor.UIElement.Label;

namespace winS.UnityEditor
{
    /// <summary>
    /// Provides additional functionality base on <see cref="global::UnityEditor.EditorWindow"/>.
    /// </summary>
    public abstract class EditorWindow : global::UnityEditor.EditorWindow, IHasCustomMenu
    {
        public static readonly string defaultSaveNotification = "已保存";

        /// <summary>
        /// This window is closing?
        /// </summary>
        protected bool closing { get; private set; }
        /// <summary>
        /// Is only used in editor mode?
        /// </summary>
        protected virtual bool editorModeOnly => false;
        /// <summary>
        /// Save on window close?
        /// </summary>
        protected virtual bool saveOnClose => true;
        /// <summary>
        /// Response save command (Ctrl+S)?
        /// </summary>
        protected virtual bool responseSaveCommand => true;
        /// <summary>
        /// Notification shown when window saved.
        /// </summary>
        protected virtual string saveNotification => defaultSaveNotification;

        private Notification notification;

        internal virtual void OnEnable()
        {
            //确保根元素有焦点并且能被拾取, 否则无法接收事件
            rootVisualElement.focusable = true;
            rootVisualElement.pickingMode = PickingMode.Position;
            rootVisualElement.RegisterCallback<KeyDownEvent>(KeyDownCallback);
            if (editorModeOnly && EditorApplication.isPlayingOrWillChangePlaymode)
            {
                Editor.onExitPlayMode += OnExitPlayModeCallback;
                Icon icon = EditorElementUtility.elementFactory.CreateIcon(PackageAssets.Icons.tips, 32f);
                icon.style.SetMarginBottom(6f);
                Label label = EditorElementUtility.elementFactory.CreateLabel($"此窗口不支持在播放模式下运行", 14f);
                label.style.SetColor(ColorLevelUtility.GetColor(16));
                label.style.alignSelf = Align.Center;

                Column column = EditorElementUtility.MergeToColumn(icon, label, 1f);
                column.style.justifyContent = Justify.Center;
                rootVisualElement.Add(column);
                return;
            }
            OnCreate();
        }

        internal virtual void OnDisable()
        {
            closing = true;
            Editor.onExitPlayMode -= OnExitPlayModeCallback;
            if (editorModeOnly && EditorApplication.isPlayingOrWillChangePlaymode) return;
            if (saveOnClose) SaveWindow();
            OnClose();
        }

        /// <summary>
        /// Save the Editor window.
        /// </summary>
        public virtual void SaveWindow()
        {
            OnSaveWindow();
            ShowNotification(saveNotification);
        }

        /// <summary>
        /// Window created callback.
        /// </summary>
        protected virtual void OnCreate()
        {
        }
        /// <summary>
        /// Window close callback.
        /// </summary>
        protected virtual void OnClose()
        {
        }
        /// <summary>
        /// Window save callback.
        /// </summary>
        protected virtual void OnSaveWindow()
        {
        }

        protected void ShowNotification(string message, float duration = 2f)
        {
            if (notification == null) rootVisualElement.Add(notification = new Notification());
            notification.Show(message, duration);
        }

        protected virtual void AddItemsToMenu(GenericMenu genericMenu)
        {
            genericMenu.AddItem(new GUIContent("重新加载"), false, () =>
            {
                OnDisable();
                rootVisualElement.Clear();
                if (notification != null) rootVisualElement.Add(notification);
                OnEnable();
            });
        }

        private void KeyDownCallback(KeyDownEvent keyDownEvent)
        {
            if (!responseSaveCommand || !keyDownEvent.ctrlKey || keyDownEvent.keyCode != KeyCode.S) return;
            SaveWindow();
            keyDownEvent.StopPropagation();
        }
        private void OnExitPlayModeCallback()
        {
            rootVisualElement.Clear();
            OnCreate();
        }

        void IHasCustomMenu.AddItemsToMenu(GenericMenu menu)
        {
            AddItemsToMenu(menu);
        }

        private class Notification : VisualElement
        {
            private const float animationDuration = 0.24f;

            private readonly Label label;

            private readonly IVisualElementScheduledItem showSchedule;
            private readonly IVisualElementScheduledItem hideSchedule;

            public Notification()
            {
                style.position = Position.Absolute;
                style.alignSelf = Align.Center;
                style.SetBottom(20f, LengthUnit.Percent);
                style.SetBackgroundColor(new Color32(0, 0, 0, 128));
                style.SetPadding(20f);
                style.SetBorderRadius(8f);
                style.SetScale(0f);
                style.SetTransition("opacity", animationDuration, EasingMode.EaseOutCubic);
                style.SetTransition("scale", animationDuration, EasingMode.EaseOutCubic);
                Add(label = EditorElementUtility.elementFactory.CreateLabel(16f));

                showSchedule = schedule.Execute(() =>
                {
                    label.MarkDirtyRepaint();
                    style.SetScale(1f);
                    style.SetOpacity(1f);
                });
                hideSchedule = schedule.Execute(() =>
                {
                    style.SetScale(0f);
                    style.SetOpacity(0f);
                });
                showSchedule.Pause();
                hideSchedule.Pause();
            }

            public void Show(string message, float duration)
            {
                BringToFront();
                label.text = message;
                showSchedule.ExecuteLater(10);
                hideSchedule.ExecuteLater((long)(duration * 1000f));
            }
        }
    }
}